package com.nulldev.util.data.Arrays.arrays;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Spliterator;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil;

/**
 * Originally contained two algorithms, unsynchronized and COAL. <br />
 * Replaced with the dual-locking algorithm which locks on READ and WRITE,
 * ensuring memory operation synchronization. (in theory) <br />
 * <br />
 * COAL: (Copied Often Array List) <br />
 * See HybridCopyOnWriteArrayList. (layered upon CopyOnWriteArrayList) <br />
 */
public class FastArrayList<T> extends ArrayList<T> implements Serializable {

	private static final long serialVersionUID = -2269018708027226249L;

	private final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock(false);

	/**
	 * Construct a an empty list.
	 */
	public FastArrayList() {
		super(8);
	}

	public FastArrayList(final boolean fastMode) {
		/* fastMode is a NO-OP */
		super(8);
	}

	/**
	 * Construct an empty list with the specified capacity.
	 *
	 * @param capacity The initial capacity of the empty list
	 */
	public FastArrayList(final int capacity) {
		super(capacity);
	}

	/**
	 * Construct an empty list with the specified capacity.
	 *
	 * @param capacity The initial capacity of the empty list
	 */
	public FastArrayList(final int capacity, final boolean fastMode) {
		super(capacity);
		/* fastMode is a NO-OP */
	}

	/**
	 * Construct a list containing the elements of the specified collection, in the
	 * order they are returned by the collection's iterator.
	 *
	 * @param collection The collection whose elements initialize the contents of
	 *                   this list
	 */
	public FastArrayList(final Collection<? extends T> collection) {
		super(collection);
	}

	/**
	 * Construct a list containing the elements of the specified collection, in the
	 * order they are returned by the collection's iterator.
	 *
	 * @param collection The collection whose elements initialize the contents of
	 *                   this list
	 */
	public FastArrayList(final Collection<? extends T> collection, final boolean fastMode) {
		super(collection);
		/* fastMode is a NO-OP */
	}

	@Override
	public T get(final int index) {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			return super.get(index);
		} finally {
			rl.unlock();
		}
	}

	@Override
	public boolean remove(final Object s) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			return super.remove(s);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public T remove(final int index) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			return super.remove(index);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public int size() {
		/* No lock required, we are reading an internal field. */
		return super.size();
	}

	@Override
	public Stream<T> stream() {
		return __do_ugly_workaround(false);
	}

	@Override
	public Stream<T> parallelStream() {
		return __do_ugly_workaround(true);
	}

	private Stream<T> __do_ugly_workaround(final boolean parallel) {
		/* Fugly workaround for java.util.ConcurrentModificationException */
		@SuppressWarnings("unchecked")
		final List<T> duplicate = (List<T>) clone();
		final Spliterator<T> sp = duplicate.spliterator();
		return StreamSupport.stream(sp, parallel).onClose(() -> {
			final boolean valuesChanged = ArrayUtils.referenceEquals(FastArrayList.this, duplicate);
			if (valuesChanged) {
				final WriteLock wl = this.LOCK.writeLock();
				wl.lock();
				try {
					super.clear();
					super.addAll(duplicate);
				} finally {
					wl.unlock();
				}
			} else {
				/* No action required */
			}
		});
	}

	@Override
	public boolean containsAll(final Collection<?> c) {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			return super.containsAll(c);
		} finally {
			rl.unlock();
		}
	}

	@Override
	public Spliterator<T> spliterator() {
		/* In theory all write operations are redirected to ours... */
		return super.spliterator();
	}

	@Override
	public boolean equals(Object o) {
		return super.equals(o);
	}

	@Override
	public boolean add(final T e) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			return super.add(e);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public void add(final int index, final T element) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			super.add(index, element);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public boolean addAll(final Collection<? extends T> c) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			return super.addAll(c);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public boolean addAll(final int index, final Collection<? extends T> c) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			return super.addAll(index, c);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public int hashCode() {
		return super.hashCode();
	}

	@Override
	public void clear() {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			super.clear();
		} finally {
			wl.unlock();
		}
	}

	@Override
	public boolean contains(final Object obj) {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			return super.contains(obj);
		} finally {
			rl.unlock();
		}
	}

	@Override
	public Object clone() {
		return super.clone();
	}

	@Override
	public boolean isEmpty() {
		/* No lock required, we simply check if internal size is zero. */
		return super.isEmpty();
	}

	@Override
	public Iterator<T> iterator() {
		/* In theory all write operations are redirected to ours... */
		return super.iterator();
	}

	@Override
	public void ensureCapacity(final int minCapacity) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			super.ensureCapacity(minCapacity);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public Object[] toArray() {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			return super.toArray();
		} finally {
			rl.unlock();
		}
	}

	@SuppressWarnings("hiding")
	@Override
	public <T> T[] toArray(final T[] a) {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			return super.toArray(a);
		} finally {
			rl.unlock();
		}
	}

	@Override
	public T set(final int index, final T element) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			return super.set(index, element);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public void trimToSize() {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			super.trimToSize();
		} finally {
			wl.unlock();
		}
	}

	@Override
	public int indexOf(final Object obj) {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			return super.indexOf(obj);
		} finally {
			rl.unlock();
		}
	}

	@Override
	public int lastIndexOf(final Object obj) {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			return super.lastIndexOf(obj);
		} finally {
			rl.unlock();
		}
	}

	@Override
	public ListIterator<T> listIterator() {
		/* In theory all write operations are redirected to ours... */
		return super.listIterator();
	}

	@Override
	public ListIterator<T> listIterator(final int index) {
		/* In theory all write operations are redirected to ours... */
		return super.listIterator(index);
	}

	@Override
	public void sort(Comparator<? super T> c) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			super.sort(c);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public List<T> subList(final int fromIndex, final int toIndex) {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			return super.subList(fromIndex, toIndex);
		} finally {
			rl.unlock();
		}
	}

	@Override
	public boolean removeAll(final Collection<?> c) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			return super.removeAll(c);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public boolean removeIf(final Predicate<? super T> filter) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			return super.removeIf(filter);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public void replaceAll(final UnaryOperator<T> operator) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			super.replaceAll(operator);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		final WriteLock wl = this.LOCK.writeLock();
		wl.lock();
		try {
			return super.retainAll(c);
		} finally {
			wl.unlock();
		}
	}

	@Override
	public void forEach(Consumer<? super T> action) {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			super.forEach(action);
		} finally {
			rl.unlock();
		}
	}

	public T getLast() {
		final ReadLock rl = this.LOCK.readLock();
		rl.lock();
		try {
			return super.get(size() - 1);
		} finally {
			rl.unlock();
		}
	}

	@Override
	public String toString() {
		return "FastArrayList@" + MathUtil.toHex(super.hashCode()) + "{" + super.toString() + "}";
	}
}
